/*
 * CLASS CAMERA
 * ============
 * Manages the eye position and the position of the image plate or Canvas
 * where we will build images.  
 * The canvas position is the central point in the plate.
 * Camera class can generate light beams, so we can detect collitions with 
 * visual objects using the Ray Caster
 */
package rayTracer;

import static rayTracer.ApplicationInterface.*;

import java.util.ArrayList;

import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import static java.lang.Math.*;

public class Camera {
	public CAMERA_TYPE type;
	public double fdist;
	public double lensr;
	public Point3d eyePosition;
	public Point3d canvasPosition;	
	public ImagePlate canvas;	
	public double visualAngle;
	public Vector3d verticalReferenceVersor;
	
	//This variables are canvas info denpendant
	double stepSize;
	Vector3d cameraDirectionVersor;
	Vector3d parallelVersorToCanvas;
	Point3d upperLeftPoint;
	
	/*
	 * By default this constructor takes default definitions in LibInterface
	 */
	public Camera(){	
		
		type = CAMERA_TYPE.PINHOLE;
		fdist = 0.0;
		lensr = 0.0;
		eyePosition = new Point3d(CAMERA_POSITION);
		canvasPosition = new Point3d(CANVAS_POSITION);
		canvas = new ImagePlate(WIDTH, HEIGHT);
		visualAngle = VISUAL_ANGLE;
		
		verticalReferenceVersor = new Vector3d();
		setCameraVerticalReference( new Vector3d(VERT_REFERENCE) );		
		setCanvasDependantVariables();		
	}	
	
	public Camera( int canvasWidth, int canvasHeight ){		
		type = CAMERA_TYPE.PINHOLE;
		fdist = 0.0;
		lensr = 0.0;
		eyePosition = new Point3d(CAMERA_POSITION);
		canvasPosition = new Point3d(CANVAS_POSITION);
		canvas = new ImagePlate( canvasWidth, canvasHeight );
		visualAngle = VISUAL_ANGLE;
		
		verticalReferenceVersor = new Vector3d(VERT_REFERENCE);		
		
		setCanvasDependantVariables();
	}
	
	public Camera( int canvasWidth, int canvasHeight, double visualAngle ){
		this();
		setVisualAngle(visualAngle);		
		canvas = new ImagePlate( canvasWidth, canvasHeight );
		setCanvasDependantVariables();
	}
	
	public Camera(Point3d eyePosition, Point3d platePosition, Vector3d verticalReference, int canvasWidth, int canvasHeight){
		this();
		this.eyePosition = new Point3d(eyePosition);
		this.canvasPosition = new Point3d(platePosition);
		verticalReferenceVersor = new Vector3d( verticalReference );
		canvas = new ImagePlate( canvasWidth, canvasHeight );
		setCanvasDependantVariables();
	}
	
	private void setCanvasDependantVariables(){						
		stepSize =  ( 2 * tan( Math.toRadians(visualAngle/2) ) ) / canvas.width ;		
		//Calculate direction versor between the camera and the canvas
		cameraDirectionVersor = new Vector3d();
		cameraDirectionVersor.sub( canvasPosition, eyePosition);
		cameraDirectionVersor.normalize();
		
		//Finds out the parallel versor to the canvas plane
		parallelVersorToCanvas = new Vector3d();				
		parallelVersorToCanvas.cross(cameraDirectionVersor, verticalReferenceVersor);		
		parallelVersorToCanvas.normalize();
		
		verticalReferenceVersor.cross(parallelVersorToCanvas, cameraDirectionVersor);
		verticalReferenceVersor.normalize();
	}
	
	public void setVisualAngle( double otherAngle ){		
		this.visualAngle = otherAngle % 360;
		setCanvasDependantVariables();		
	}
	public double getVisualAngle(){
		return visualAngle;
	}
	
	public void setCameraVerticalReference( Vector3d otherReference ){		
		verticalReferenceVersor = new Vector3d( otherReference );		
	}
	
	public Vector3d getCameraDirection(){		
		return new Vector3d(cameraDirectionVersor);
	}
	
	public ImagePlate getCanvas(){
		return canvas;
	}
		
	public double getCanvasDistance(){
		return canvasPosition.distance(eyePosition);
	}
	
	public void setCanvasDistance(double distance){
		//Get the versor in the direction of the new canvas position
		//Vector3d ans = getCameraDirection();
		Vector3d ans = getCameraDirection();
		
		//Scale it with new distance. Now we have a vector that is the right
		//length. Just add the eyePosition, and we have new the canvasPosition		
		ans.scaleAdd(distance, eyePosition);
	}

	public String toString(){
		StringBuffer str = new StringBuffer();
		str.append("Eye Position : " + eyePosition + "\nCanvas Position : " +  canvasPosition + "\nVertical Reference Point = " + verticalReferenceVersor + "\nVisual Angle (Sexagesimal) : " + visualAngle + "\nCanvas Info : " + canvas);
		return str.toString();
	}
	  /*
     * We need to traverse the canvas in discrete steps. For doing this we
     * need 2 things:
     * 
     * 1 - The stepSize
     *     ============
     *              Formula used:
     *                                                             canvasWidth * stepSize
     *                      tan(visualAngle/2)  =   ------------------------
     *                                                               2 * canvasDistance
     * 
     *              where every variable is known except for the stepSize need to 
     *              traverse the whole canvas plate
     * 
     * 2 - Canvas Parallel Direction 
     *         =========================
     *              To move among points that belong to the canvas plane. For this we
     *              used a simplified version at this time. We asume the camera will
     *              always has a direction parallel to the Z axis.
     *              In this case, the camera direction and the X-axis will be 
     *              perpendicular to each other. Thus, the vectorial product will give
     *              us the versor for traverse the canvas
     */
    public ArrayList<Ray> generateRays(){
            ArrayList<Ray> rays = new ArrayList<Ray>();
            Vector3d parallelVersorToCanvas = new Vector3d();
            Vector3d diffPosition = new Vector3d();
            Point3d upperLeftPoint = new Point3d();
            
            //Calculate the step size from the formula stated above
            int width = canvas.width;
            int height = canvas.height;
            double angle = toRadians(visualAngle/2);
            double stepSize =  2 *  ( this.getCanvasDistance() / width ) * tan(angle);
            stepSize = abs(stepSize);               
            
            //Finds out the parallel versor to the canvas plane
            Vector3d cameraDirectionVersor = this.getCameraDirection();             
            //Vector3d verticalReferenceVersor = getCameraVerticalReference();
            Vector3d verticalReferenceVersor = new Vector3d( this.verticalReferenceVersor );
            verticalReferenceVersor.normalize();
            parallelVersorToCanvas.cross(cameraDirectionVersor, verticalReferenceVersor);           
            
            //Calculate the upper left corner of the plate
            Vector3d diffHorizontal = new Vector3d(parallelVersorToCanvas);
            Vector3d diffVertical = new Vector3d(verticalReferenceVersor);
            
            diffVertical.scale( getScaleFactor(height, stepSize) );
            diffHorizontal.scale( getScaleFactor(width, stepSize) );
            diffPosition.sub( diffVertical, diffHorizontal );               
            upperLeftPoint.add(canvasPosition, diffPosition); 
                            
            //Traverse the canvas and store the rays                
            Point3d nextRowPointer = new Point3d(upperLeftPoint);
            Vector3d deltaHorizontal = new Vector3d(parallelVersorToCanvas);
            Vector3d deltaVertical = new Vector3d(verticalReferenceVersor);         
            deltaVertical.scale(stepSize);
            deltaHorizontal.scale(stepSize);                
            for( int y = 0 ; y < height ; y++ ) {
                    Point3d currentCanvasPoint = new Point3d(nextRowPointer);
                    for ( int x = 0; x < width; x++) {
                            System.out.println("(row=" + y + ", col=" + x +")Current CanvasPoint = " + currentCanvasPoint);//DEBUG
                            rays.add(new Ray( new Point3d(eyePosition), new Point3d(currentCanvasPoint)));
                            currentCanvasPoint.add(deltaHorizontal); 
                    }
                    nextRowPointer.sub(deltaVertical);
            }               
            return rays;            
    }       
	/*
	 * This method allows to determine the scale factor to reach the canvas vertical or horizontal limit
	 * takes into account if the dimension is even or odd number
	 */	
	private double getScaleFactor( int dimension, double stepSize ){
		if( dimension % 2 == 0 )
			return stepSize/2 * (dimension-1);
		else
			return stepSize * (dimension/2);
	}
}
